﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Activators;
using MugenInjection.Bindings;
using MugenInjection.Bindings.Builders;
using MugenInjection.Core;
using MugenInjection.Delegates;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Parameters;
using MugenInjection.Scope;
using MugenInjection.Syntax;
using Should;
using TestModels.Class;
using TestModels.FakeObject;
using TestModels.Interface;
using Action = System.Action;

namespace MugenInjectionTest.Bindings
{
    [TestClass]
    public class BindingBuilderTypeTest : TestBase
    {
        [TestMethod]
        public void CreateBindingTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty), typeof(I1) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass));

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.ShouldNotBeNull();
                typeBinding.Services.Contains(typeof(IEmpty)).ShouldBeTrue();
                typeBinding.Services.Contains(typeof(I1)).ShouldBeTrue();
                typeBinding.ActivatingActions.ShouldBeEmpty();
                typeBinding.ActivatedActions.ShouldBeEmpty();

                typeBinding.DeactivatedActions.ShouldBeEmpty();
                typeBinding.TryDisposeObject.ShouldBeFalse();
                typeBinding.Parameters.ShouldBeEmpty();
                typeBinding.Priority.ShouldEqual(BindingPriority.Standard);

                Action action = () => new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty), typeof(IEmpty) }, injector);
                action.ShouldThrow<ArgumentException>();
            }
        }

        [TestMethod]
        public void WithConditionWhenIntoTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass));
                syntax.WhenInto(typeof(MemberInfo));

                Action action = () => syntax.WhenInto(typeof(object));
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(MemberInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                typeBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenIntoGenericTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax bindingSyntax = bindingBuilder;
                var syntax = bindingSyntax.To(typeof(SimpleClass));
                syntax.WhenInto<MemberInfo>();

                Action action = () => syntax.WhenInto(typeof(object));
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(MemberInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                typeBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenIntoIsAssignableTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass));
                syntax.WhenIntoIsAssignable(typeof(MemberInfo));

                Action action = () => syntax.WhenIntoIsAssignable(typeof(object));
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(MemberInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                typeBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenIntoIsAssignableGenericTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass));
                syntax.WhenIntoIsAssignable<MemberInfo>();

                Action action = () => syntax.WhenIntoIsAssignable(typeof(object));
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(MemberInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                typeBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenNamespaceEqualTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass));
                syntax.WhenNamespaceEqual("System");

                Action action = () => syntax.WhenNamespaceEqual("System");
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(object).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                typeBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenClassHasAttributeTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass));
                syntax.WhenClassHasAttribute(typeof(SimpleAttribute));

                Action action = () => syntax.WhenClassHasAttribute(typeof(SimpleAttribute));
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(ClassHasAttribute).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                typeBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenClassHasAttributeGenericTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass));
                syntax.WhenClassHasAttribute<SimpleAttribute>();

                Action action = () => syntax.WhenClassHasAttribute(typeof(SimpleAttribute));
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(ClassHasAttribute).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                typeBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass));
                syntax.When(context => context.TypeInto == typeof(object));

                Action action = () => syntax.When(context => context.TypeInto == typeof(object));
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                typeBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(object).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                typeBinding.Priority.ShouldEqual(BindingPriority.Normal);
            }
        }

        [TestMethod]
        public void PriorityTest()
        {
            using (var injector = GetInjector())
            {
                //Test priority
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).SetStandardPriority();

                var methodBinding = (TypeBinding)bindingBuilder.Build();
                methodBinding.Priority.ShouldEqual(BindingPriority.Standard);

                //Low
                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).SetLowPriority();

                methodBinding = (TypeBinding)bindingBuilder.Build();
                methodBinding.Priority.ShouldEqual(BindingPriority.Low);

                //Normal
                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).SetNormalPriority();

                methodBinding = (TypeBinding)bindingBuilder.Build();
                methodBinding.Priority.ShouldEqual(BindingPriority.Normal);

                //High
                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).SetHighPriority();

                methodBinding = (TypeBinding)bindingBuilder.Build();
                methodBinding.Priority.ShouldEqual(BindingPriority.High);

                //Custom
                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass));
                syntax.SetPriority(BindingPriority.High);

                methodBinding = (TypeBinding)bindingBuilder.Build();
                methodBinding.Priority.ShouldEqual(BindingPriority.High);

                Action action = () => syntax.SetNormalPriority();
                action.ShouldThrow<InvalidBindingException>();
            }
        }

        [TestMethod]
        public void NamedBindingTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;

                var syntax = binding.To(typeof(SimpleClass)).NamedBinding("NamedBinding");
                Action action = () => syntax.NamedBinding("Test");
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                var dictionary = new Dictionary<string, object>();
                var bindingContext = new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null, injector, injector, InjectorUtilsInternal.EmptyInjectionParams, dictionary);
                typeBinding.CanResolve(bindingContext).ShouldBeFalse();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, "NamedBinding");
                typeBinding.CanResolve(bindingContext).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void KeyedBindingTest()
        {
            var key = new object();
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;

                var syntax = binding.To(typeof(SimpleClass)).KeyedBinding(key);
                Action action = () => syntax.KeyedBinding(key);
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                var dictionary = new Dictionary<string, object>();
                var bindingContext = new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null, injector, injector, InjectorUtilsInternal.EmptyInjectionParams, dictionary);
                typeBinding.CanResolve(bindingContext).ShouldBeFalse();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, key);
                typeBinding.CanResolve(bindingContext).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void NamedBindingWithConditionTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;

                var syntax = binding.To(typeof(SimpleClass)).WhenInto<SimpleClass>().NamedBinding("NamedBinding");
                Action action = () => syntax.NamedBinding("Test");
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                var dictionary = new Dictionary<string, object>();
                var bindingContext = new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null, injector, injector, InjectorUtilsInternal.EmptyInjectionParams, dictionary);
                typeBinding.CanResolve(bindingContext).ShouldBeFalse();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, "NamedBinding");
                typeBinding.CanResolve(bindingContext).ShouldBeFalse();

                bindingContext = new BindingContext(typeof(IEmpty), typeof(SimpleClass).GetTypeInfo(), null, injector, injector, InjectorUtilsInternal.EmptyInjectionParams, dictionary);
                typeBinding.CanResolve(bindingContext).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void TryDisposeObjectTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass)).TryDisposeObjects();
                Action action = () => syntax.TryDisposeObjects();
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.TryDisposeObject.ShouldBeTrue();
            }
        }

        [TestMethod]
        public void WithSettingTest()
        {
            using (var injector = GetInjector())
            {
                var settingObj = new object();
                var settingObj1 = new object();

                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).WithSetting("test", settingObj).WithSetting("test1", settingObj1);

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Settings.Get("test").ShouldEqual(settingObj);
                typeBinding.Settings.Get("test1").ShouldEqual(settingObj1);
            }
        }

        [TestMethod]
        public void BindingBuilderTest()
        {
            using (var injector = GetInjector())
            {
                bool isBuilded = false;
                bool isBuilding = false;
                var bindingBuilder = new BindingBuilder<I1, object, object, object>(new[] { typeof(I1) }, injector).To(typeof(SimpleClass));

                bindingBuilder.Building += builder =>
                {
                    isBuilded.ShouldBeFalse();
                    builder.ShouldNotBeNull();
                    isBuilding = true;
                    return builder;
                };
                bindingBuilder.Builded += builder =>
                {
                    var binding = builder.Build();
                    isBuilded = true;
                    isBuilding.ShouldBeTrue();
                    binding.ShouldBeType<TypeBinding>();
                    return binding;
                };

                bindingBuilder.Services.Contains(typeof(I1)).ShouldBeTrue();
                bindingBuilder.Injector.ShouldEqual(injector);

                (bindingBuilder.Build() is TypeBinding).ShouldBeTrue();
                isBuilded.ShouldBeTrue();
            }
        }

        [TestMethod]
        public void CallbackEventsTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                BindingActivatedDelegate<object> activated = (IBinding binding1, IBindingContext context, ref object instance) => { };
                var activating = new Action<IBindingContext>(o => { });
                var disposed = new Action<IBinding>(o => { });
#if NET2
                var onReleaseObject = new MugenInjection.Delegates.Action<object, bool>((o, b) => { });
#else
                var onReleaseObject = new Action<object, bool>((o, b) => { });
#endif

                var objectSyntax = binding
                    .To(typeof(SimpleClass))
                    .InSingletonScope()
                    .OnActivated(activated)
                    .OnDisposed(disposed)
                    .OnActivating(activating)
                    .OnReleaseObject(onReleaseObject);

                var methodBinding = (TypeBinding)bindingBuilder.Build();

                methodBinding.ActivatedActions.Contains(activated).ShouldBeTrue();
                methodBinding.ActivatingActions.Contains(activating).ShouldBeTrue();
                methodBinding.DeactivatedActions.Contains(disposed).ShouldBeTrue();
                methodBinding.ScopeLifecycle.ReleaseObjectActions.Contains(onReleaseObject).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void ConstructorParametersTest()
        {
            using (var injector = GetInjector())
            {
                IGenericBindingSyntax<IEmpty> bindingBuilder = (IGenericBindingSyntax<IEmpty>)new BindingBuilder<IEmpty, object, object, object>(new[] { typeof(IEmpty) }, injector);
                var binding = bindingBuilder;
#if NOEXPRESSION
                binding.To(typeof(SimpleClass)).WithConstructorArgument("Test", new object());
#else
                binding.To<SimpleClass>()
                 .WithConstructorArguments(context => new SimpleClass("test"));
#endif
                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Parameters.Count.ShouldEqual(1);
                typeBinding.Parameters.OfType<ConstructorParameter>().Any().ShouldBeTrue();

                bindingBuilder = new BindingBuilder<IEmpty, object, object, object>(new[] { typeof(IEmpty) }, injector);
                binding = bindingBuilder;
#if NOEXPRESSION
                var syntax = binding.To(typeof(SimpleClass)).WithConstructorArgument("Test", context => new object());
                Action action = () => syntax.WithConstructorArgument("Test", new object());
#else
                var syntax = binding.To<SimpleClass>().WithConstructorArguments(context => new SimpleClass("test"));
                Action action = () => syntax.WithConstructorArguments(context => new SimpleClass("string"));
#endif
                action.ShouldThrow<InvalidBindingException>();
            }
        }

        [TestMethod]
        public void MethodParametersTest()
        {
            using (var injector = GetInjector())
            {
                //Method params
                IGenericBindingSyntax<SimpleClass> bindingBuilder = new BindingBuilder<SimpleClass, object, object, object>(new[] { typeof(IEmpty) }, injector);
                var binding = bindingBuilder;

#if NOEXPRESSION
                binding.To(typeof(SimpleClass)).WithMethodArgument("MethodTest", "Test", new object());
#else
                binding.To<SimpleClass>().WithMethodArguments((context, @class) => @class.Method("test"));
#endif
                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Parameters.Count.ShouldEqual(1);
                typeBinding.Parameters.OfType<MethodParameter>().Any().ShouldBeTrue();

                bindingBuilder = new BindingBuilder<SimpleClass, object, object, object>(new[] { typeof(IEmpty) }, injector);
                binding = bindingBuilder;

#if NOEXPRESSION
                var syntax = binding.To(typeof(SimpleClass)).WithMethodArgument("MethodTest", "Test", context => new object());
                Action action = () => syntax.WithMethodArgument("MethodTest", "Test", new object());
#else
                var syntax = binding.To<SimpleClass>().WithMethodArguments((context, @class) => @class.Method("test"));
                Action action = () => syntax.WithMethodArguments((context, @class) => @class.Method("test"));
#endif
                action.ShouldThrow<InvalidBindingException>();
            }
        }

        [TestMethod]
        public void PropertyParametersTest()
        {
            using (var injector = GetInjector())
            {
                IGenericBindingSyntax<SimpleClass> bindingBuilder = new BindingBuilder<SimpleClass, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                var binding = bindingBuilder;
#if NOEXPRESSION
                binding.To(typeof(SimpleClass)).WithPropertyValue("Test", new object());
#else
                binding.To<SimpleClass>().WithPropertyValue(@class => @class.Property, "tset");
#endif
                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Parameters.Count.ShouldEqual(1);
                typeBinding.Parameters.OfType<PropertyParameter>().Any().ShouldBeTrue();

                bindingBuilder = new BindingBuilder<SimpleClass, object, object, object>(new[] { typeof(IEmpty) },
                                                                                    injector);
                binding = bindingBuilder;
#if NOEXPRESSION
                var syntax = binding.To(typeof(SimpleClass))
                                    .WithPropertyValue("Test", context => new object());
                Action action = () => syntax.WithPropertyValue("Test", new object());
#else
                var syntax = binding.To<SimpleClass>().WithPropertyValue(@class => @class.Property, "test");
                Action action = () => syntax.WithPropertyValue(@class => @class.Property, "test");
#endif
                action.ShouldThrow<InvalidBindingException>();
            }
        }

        [TestMethod]
        public void CustomParametersTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<IEmpty, object, object, object>(new[] { typeof(IEmpty) }, injector);
                var binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).WithParameter(new PropertyParameter("Test", context => new object()));

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Parameters.Count.ShouldEqual(1);
                typeBinding.Parameters.OfType<PropertyParameter>().Any().ShouldBeTrue();
            }
        }

        [TestMethod]
        public void TransientScopeTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).InTransientScope();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.ScopeLifecycle.ShouldBeType<TransientScopeLifecycle>();
            }
        }

        [TestMethod]
        public void SingletonScopeTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).InSingletonScope();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.ScopeLifecycle.ShouldBeType<SingletonScopeLifecycle>();
            }
        }

        [TestMethod]
        public void ThreadScopeTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).InThreadScope();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.ScopeLifecycle.ShouldBeType<ThreadScopeLifecycle>();
            }
        }

        [TestMethod]
        public void ManagedScopeTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).InManagedScope();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.ScopeLifecycle.ShouldBeType<ManagedScopeLifecycle>();
            }
        }

        [TestMethod]
        public void UowScopeTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).InUnitOfWorkScope();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.ScopeLifecycle.ShouldBeType<UnitOfWorkScopeLifecycle>();
            }
        }

        [TestMethod]
        public void WeakReferenceScopeTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).InWeakReferenceScope();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.ScopeLifecycle.ShouldBeType<WeakReferenceScopeLifecycle>();
            }
        }

        [TestMethod]
        public void CustomScopeTest()
        {
            using (var injector = GetInjector())
            {
                var scopeLifecycle = new UnitOfWorkScopeLifecycle();
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass));
                syntax.InScope(scopeLifecycle);

                Action action = () => syntax.InScope(scopeLifecycle);
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.ScopeLifecycle.ShouldEqual(scopeLifecycle);
            }
        }

        [TestMethod]
        public void DefaultActivatorShouldNotBeNullTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass));

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Activator.ShouldNotBeNull();
            }
        }


        [TestMethod]
        public void UseReflectionActivatorTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).UseReflectionActivator();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Activator.ShouldBeType<ReflectionActivator>();
            }
        }

#if !NETFX_CORE
        [TestMethod]
        public void UseEmitActivatorTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).UseEmitActivator();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Activator.ShouldBeType<EmitActivator>();
            }
        }
#endif

        [TestMethod]
        public void UseCustomActivatorTest()
        {
            using (var injector = GetInjector())
            {
                var activator = new ReflectionActivator();
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.To(typeof(SimpleClass));
                syntax.UseCustomActivator(activator);

                Action action = () => syntax.UseCustomActivator(activator);
                action.ShouldThrow<InvalidBindingException>();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Activator.ShouldEqual(activator);
            }
        }

        [TestMethod]
        public void UseCustomConstuctorResolverTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder =
                    new BindingBuilder<object, object, object, object>(new[] { typeof(ActivatorTestModel) }, injector);
                var binding = bindingBuilder;
                binding.To(typeof(ActivatorTestModel)).UseCustomConstructorResolver(new FakeConstructorResolver());

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.ConstructorResolver.ShouldBeType<FakeConstructorResolver>();

                bindingBuilder = new BindingBuilder<object, object, object, object>(
                    new[] { typeof(ActivatorTestModel) }, injector);
                binding = bindingBuilder;
                var syntax =
                     binding.To(typeof(ActivatorTestModel))
                            .UseCustomConstructorResolver(() => new FakeConstructorResolver());

                Action action = () => syntax.UseCustomConstructorResolver(new FakeConstructorResolver());
                action.ShouldThrow<InvalidBindingException>();

                typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.ConstructorResolver.ShouldBeType<FakeConstructorResolver>();
            }
        }

        [TestMethod]
        public void TakeConstructorTest()
        {
            using (var injector = GetInjector())
            {
                //Default constructor.
                var bindingBuilder =
                    new BindingBuilder<object, object, object, object>(new[] { typeof(ActivatorTestModel) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(ActivatorTestModel));

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                ((DefaultConstructorResolver)typeBinding.ConstructorResolver).ConstructorInfo.ShouldBeNull();


                //Empty constructor
                bindingBuilder = new BindingBuilder<object, object, object, object>(
                    new[] { typeof(ActivatorTestModel) }, injector);
                binding = bindingBuilder;
                binding.To(typeof(ActivatorTestModel)).TakeConstructor();

                typeBinding = (TypeBinding)bindingBuilder.Build();
                ((DefaultConstructorResolver)typeBinding.ConstructorResolver).ConstructorInfo.GetParameters().ShouldBeEmpty();

                //With arguments
                bindingBuilder = new BindingBuilder<object, object, object, object>(
                    new[] { typeof(ActivatorTestModel) }, injector);
                binding = bindingBuilder;
                var syntax = binding.To(typeof(ActivatorTestModel));
                syntax.TakeConstructor(new[] { typeof(string) });

                Action action = () => syntax.TakeConstructor();
                action.ShouldThrow<InvalidBindingException>();

                typeBinding = (TypeBinding)bindingBuilder.Build();

                var parameters = ((DefaultConstructorResolver)typeBinding.ConstructorResolver).ConstructorInfo.GetParameters();
                parameters.Length.ShouldEqual(1);
                parameters[0].ParameterType.ShouldEqual(typeof(string));
            }
        }


#if !NOEXPRESSION
        [TestMethod]
        public void UseExpressionActivatorTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;
                binding.To(typeof(SimpleClass)).UseExpressionActivator();

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Activator.ShouldBeType<ExpressionActivator>();
            }
        }

        [TestMethod]
        public void ToExpressionTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder =
                    (IGenericBindingSyntax<IEmpty>)
                    new BindingBuilder<IEmpty, object, object, object>(new[] { typeof(IEmpty) }, injector);
                bindingBuilder.To(context => new SimpleClass());

                var typeBinding = (TypeBinding)bindingBuilder.Build();
                typeBinding.Parameters.ShouldBeEmpty();
                typeBinding.TypeTo.ShouldEqual(typeof(SimpleClass));
                ((DefaultConstructorResolver)typeBinding.ConstructorResolver).ConstructorInfo.GetParameters().ShouldBeEmpty();

                //With constructor argument
                IGenericBindingSyntax<ConstructorResolverTestModel> bindingBuilder1 =
                    new BindingBuilder<ConstructorResolverTestModel, object, object, object>(
                        new[] { typeof(ConstructorResolverTestModel) }, injector);
                bindingBuilder1.To(context => new ConstructorResolverTestModel("Test", "Test2", Get("Test3")));

                typeBinding = (TypeBinding)bindingBuilder1.Build();
                typeBinding.Parameters.Count.ShouldEqual(3);
                typeBinding.TypeTo.ShouldEqual(typeof(ConstructorResolverTestModel));
                ((DefaultConstructorResolver)typeBinding.ConstructorResolver).ConstructorInfo.GetParameters().Length.ShouldEqual(3);

                var strings = new[] { "Test", "Test2", "Test3" };
                foreach (var injectionParameter in typeBinding.Parameters)
                {
                    var value = injectionParameter.GetValue(null);
                    strings.Contains(value).ShouldBeTrue();
                }

                //With constructor arguments and parameters
                var bindingBuilder2 =
                    (IGenericBindingSyntax<ParameterTestModel>)
                    new BindingBuilder<ParameterTestModel, object, object, object>(new[] { typeof(ParameterTestModel) },
                                                                                   injector);
                bindingBuilder2.To(context => new ParameterTestModel("Test")
                                                  {
                                                      PublicPropertyString = "Property"
                                                  });

                typeBinding = (TypeBinding)bindingBuilder2.Build();
                typeBinding.Parameters.Count.ShouldEqual(2);
                typeBinding.TypeTo.ShouldEqual(typeof(ParameterTestModel));
                ((DefaultConstructorResolver)typeBinding.ConstructorResolver).ConstructorInfo.GetParameters().Length.ShouldEqual(1);

                strings = new[] { "Test", "Property" };
                foreach (var injectionParameter in typeBinding.Parameters)
                {
                    var value = injectionParameter.GetValue(null);
                    strings.Contains(value).ShouldBeTrue();
                }

                //With constructor arguments and parameters and resolve call.
                bindingBuilder2 = new BindingBuilder<ParameterTestModel, object, object, object>(new[] { typeof(ParameterTestModel) },
                                                                                   injector);
                bindingBuilder2.To(context => new ParameterTestModel(context.Resolve<string>())
                                                  {
                                                      PublicFieldString = context.Resolve<string>(),
                                                      PublicPropertyString = "Property"
                                                  });

                typeBinding = (TypeBinding)bindingBuilder2.Build();
                typeBinding.Parameters.Count.ShouldEqual(1);
                typeBinding.TypeTo.ShouldEqual(typeof(ParameterTestModel));
                ((DefaultConstructorResolver)typeBinding.ConstructorResolver).ConstructorInfo.GetParameters().Length.ShouldEqual(1);

                strings = new[] { "Property" };
                foreach (var injectionParameter in typeBinding.Parameters)
                {
                    var value = injectionParameter.GetValue(null);
                    strings.Contains(value).ShouldBeTrue();
                }
            }
        }

        private static string Get(string par)
        {
            return par;
        }
#endif
    }
}